package com.example.demo.common.zookeeper;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;

import java.util.List;

public class ZookeeperCuratorClient {
    /** * 客户端连接地址 */
    private static final String ZK_ADDRESS = "ip:2181";
    /** * 客户端根节点 */
    private static final String ROOT_NODE = "/root";
    /** * 客户端子节点 */
    private static final String ROOT_NODE_CHILDREN = "/root/children";
    /** * 会话超时时间 */
    private final int SESSION_TIMEOUT = 20 * 1000;
    /** * 连接超时时间 */
    private final int CONNECTION_TIMEOUT = 5 * 1000;
    /** * 创建zookeeper连接实例 */
    private static CuratorFramework client = null;
    /** * 重试策略 * baseSleepTimeMs：初始的重试等待时间，单位毫秒 * maxRetries：最多重试次数 */
    private static final RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
    /** * 重试策略 * n：最多重试次数 * sleepMsBetweenRetries：重试时间间隔，单位毫秒 */
    private static final RetryPolicy retry = new RetryNTimes(3, 2000);
    static {
        // 创建Curator连接对象
        connectCuratorClient();
    }
    /** * <p> * 创建Curator连接对象 * <p/> * * @param * @return * @Date 2020/6/21 12:29 */
    public static void connectCuratorClient() {
        //老版本的方式，创建zookeeper连接客户端
        /*client = CuratorFrameworkFactory.builder(). connectString(ZK_ADDRESS). sessionTimeoutMs(5000). connectionTimeoutMs(10000). retryPolicy(retry). build();*/
        //创建zookeeper连接，新版本
        client = CuratorFrameworkFactory.newClient(ZK_ADDRESS, retry);
        //启动客户端（Start the client. Most mutator methods will not work until the client is started）
        client.start();
        System.out.println("zookeeper初始化连接成功：" + client);
    }
    public static void main(String[] args) throws Exception {
        //创建节点
        //createNode(ROOT_NODE, null);
        //createNode(ROOT_NODE_CHILDREN, "child data");
        //获取节点数据
        getNode(ROOT_NODE);
        //设置（修改）节点数据
        updateNode(ROOT_NODE, "update curator data");
        //异步设置某个节点数据
        updateNode(ROOT_NODE, "update curator data with Async");
        //删除指定节点（这个在原生zk里面，是不能直接删除有子节点的数据的）
        deleteNode(ROOT_NODE);
    }
    /** * <p> * 创建节点，并支持赋值数据内容 * <p/> * * @param nodePath * @param data * @return void * @Date 2020/6/21 12:39 */
    private static void createNode(String nodePath, String data) throws Exception {
        if (StringUtils.isEmpty(nodePath)) {
            System.out.println("节点【" + nodePath + "】不能为空");
            return;
        }
        //1、对节点是否存在进行判断，否则会报错：【NodeExistsException: KeeperErrorCode = NodeExists for /root】
        Stat exists = client.checkExists().forPath(nodePath);
        if (null != exists) {
            System.out.println("节点【" + nodePath + "】已存在，不能新增");
            return;
        } else {
            System.out.println(StringUtils.join("节点【", nodePath, "】不存在，可以新增节点！"));
        }
        //2、创建节点, curator客户端开发提供了Fluent风格的API，是一种流式编码方式，可以不断地点.点.调用api方法
        //创建永久节点（默认就是持久化的）
        //client.create().forPath(nodePath);
        //3、手动指定节点的类型
        /*client.create() .withMode(CreateMode.PERSISTENT) .forPath(nodePath);*/
        //4、如果父节点不存在，创建当前节点的父节点
        /*String node = client.create() .creatingParentsIfNeeded() .forPath(nodePath); System.out.println(node);*/
        //创建节点，并为当前节点赋值内容
        if (StringUtils.isNotBlank(data)) {
            //5、创建永久节点，并为当前节点赋值内容
            /*client.create() .forPath(nodePath, data.getBytes());*/
            String node = client.create()
                    .creatingParentsIfNeeded()
                    .forPath(nodePath, data.getBytes());
            System.out.println(node);
            //6、创建永久有序节点
            client.create().withMode(CreateMode.PERSISTENT_SEQUENTIAL).forPath(nodePath + "-sequential", data.getBytes());
            //7、创建临时节点
            client.create()
                    .withMode(CreateMode.EPHEMERAL)
                    .forPath(nodePath + "/temp", data.getBytes());
            //8、创建临时有序节点
            client.create()
                    .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
                    .forPath(nodePath + "/temp-sequential", data.getBytes());
        }
    }
    /** * <p> * 获取节点数据 * <p/> * * @param nodePath * @return void * @Date 2020/6/21 13:13 */
    private static void getNode(String nodePath) throws Exception {
        //获取某个节点数据
        byte[] bytes = client.getData().forPath(nodePath);
        System.out.println(StringUtils.join("节点：【", nodePath, "】，数据：", new String(bytes)));
        //读取zookeeper的数据，并放到Stat中
        Stat stat1 = new Stat();
        byte[] bytes2 = client.getData().storingStatIn(stat1).forPath(nodePath);
        System.out.println(StringUtils.join("节点：【", nodePath, "】，数据：", new String(bytes2)));
        System.out.println(stat1);
        //获取某个节点的所有子节点
        List<String> stringList = client.getChildren().forPath(nodePath);
        if (CollectionUtils.isEmpty(stringList)) {
            return;
        }
        //遍历节点
        stringList.forEach(System.out::println);
    }
    /** * <p> * 设置（修改）节点数据 * <p/> * * @param nodePath * @param data * @return void * @Date 2020/6/21 13:46 */
    private static void updateNode(String nodePath, String data) throws Exception {
        //更新节点
        Stat stat = client.setData().forPath(nodePath, data.getBytes());
        //指定版本号，更新节点，更新的时候如果指定数据版本的话，那么需要和zookeeper中当前数据的版本要一致，-1表示匹配任何版本
        //Stat stat = client.setData().withVersion(-1).forPath(nodePath, data.getBytes());
        System.out.println(stat);
        //异步设置某个节点数据
        Stat stat1 = client.setData().inBackground().forPath(nodePath, data.getBytes());
        if (null != stat1) {
            System.out.println(stat1);
        }
    }
    /** * <p> * 删除指定节点 * <p/> * * @param nodePath * @return void * @Date 2020/6/21 13:50 */
    private static void deleteNode(String nodePath) throws Exception {
        //删除节点
        //client.delete().forPath(nodePath);
        //删除节点，即使出现网络故障，zookeeper也可以保证删除该节点
        //client.delete().guaranteed().forPath(nodePath);
        //级联删除节点（如果当前节点有子节点，子节点也可以一同删除）
        client.delete().deletingChildrenIfNeeded().forPath(nodePath);
    }
}
